package com.getan.boot.module.system.service.persistent;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.getan.boot.common.enums.EnableStatus;
import com.getan.boot.common.utils.CollectionUtils;
import com.getan.boot.common.utils.MathUtils;
import com.getan.boot.common.utils.Preconditions;
import com.getan.boot.jdbc.service.GenericService;
import com.getan.boot.module.system.constant.DeptTypeEnum;
import com.getan.boot.module.system.constant.ErrorCode;
import com.getan.boot.module.system.entity.DeptEntity;
import com.getan.boot.module.system.entity.UserDeptEntity;
import com.getan.boot.module.system.repository.DeptRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * 部门管理
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public class DeptService extends GenericService<DeptRepository, DeptEntity> {

    private final UserDeptService userDeptService;

    /**
     * 查询所有部门，以树结构返回
     *
     * @return 部门树
     */
    public List<DeptEntity> tree() {
        List<DeptEntity> deptList = list();
        return CollectionUtils.listTree(deptList, DeptEntity::getId, DeptEntity::getPid, DeptEntity::getChildren, DeptEntity::setChildren);
    }

    /**
     * 校验部门名称是否唯一
     *
     * @param entity {id,pid,name}
     */
    public void checkName(DeptEntity entity) {
        Preconditions.check(() -> exists(Wrappers.<DeptEntity>lambdaQuery()
                .eq(DeptEntity::getPid, entity.getPid())
                .eq(DeptEntity::getName, entity.getName())), ErrorCode.DEPT_NAME_EXISTS);
    }

    /**
     * 校验部门是否存在
     *
     * @return 部门信息
     */
    private DeptEntity checkExists(Long id) {
        DeptEntity db = getById(id);
        Preconditions.isNull(db, ErrorCode.DEPT_NOT_EXISTS);
        return db;
    }

    /**
     * 校验部门是否存在
     */
    public void checkParentActive(Long id) {
        if (id == null || id.compareTo(0L) == 0) {
            return;
        }
        DeptEntity db = getById(id);
        Preconditions.isNull(db, ErrorCode.DEPT_PARENT_NOT_EXISTS);
        Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.DEPT_PARENT_DISABLED);
        Preconditions.check(() -> DeptTypeEnum.PERSONAL.match(db.getType()), ErrorCode.DEPT_PARENT_PERSONAL);
    }

    /**
     * 校验部门状态
     * - 部门是否存在
     * - 部门状态是否可用
     *
     * @return 部门信息
     */
    private DeptEntity checkActive(Long id) {
        DeptEntity db = checkExists(id);
        Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.DEPT_DISABLED);
        return db;
    }

    /**
     * 判断部门是否存在可用的子部门
     *
     * @param id 部门ID
     * @return true | false
     */
    private boolean hasChildren(Long id) {
        return exists(Wrappers.<DeptEntity>lambdaQuery()
                .eq(DeptEntity::getPid, id)
                .eq(DeptEntity::getStatus, EnableStatus.ENABLE.getCode()));
    }

    /**
     * 检查是否存在子部门，如果存在子部门，则不能调整父部门，也不能删除或者停用
     *
     * @param id 部门ID
     */
    private void checkHasChildren(Long id) {
        Preconditions.check(() -> hasChildren(id), ErrorCode.DEPT_HAS_CHILDREN);
    }

    /**
     * 判断是否可修改
     *
     * @param entity 部门
     * @return 部门信息
     */
    public DeptEntity canUpdate(DeptEntity entity) {
        DeptEntity db = checkActive(entity.getId());
        // 父部门发生变化，检查父部门状态，检查是否有子部门
        boolean changeParent = !MathUtils.equals(db.getPid(), entity.getPid());
        if (changeParent) {
            checkParentActive(entity.getPid());
            checkHasChildren(db.getId());
        }
        boolean changeType = db.getType().compareTo(entity.getType()) != 0;
        if (!changeParent && changeType && DeptTypeEnum.PERSONAL.match(entity.getType())) {
            checkHasChildren(db.getId());
        }
        if (!Objects.equals(db.getName(), entity.getName())) {
            checkName(entity);
        }
        return db;
    }

    /**
     * 判断是否可删除
     *
     * @param id 部门ID
     * @return 部门信息
     */
    public DeptEntity canDelete(Long id) {
        DeptEntity db = checkExists(id);
        // 检查是否有子部门
        if (EnableStatus.ENABLE.match(db.getStatus())) {
            checkHasChildren(id);
        }
        // 检查是否有成员
        checkHasMember(id, "删除");
        return db;
    }

    /**
     * 判断是否可停用
     *
     * @param id 部门ID
     * @return 部门信息
     */
    public DeptEntity canDisable(Long id) {
        DeptEntity db = checkActive(id);
        // 检查是否有子部门
        checkHasChildren(id);
        // 检查是否有成员
        checkHasMember(id, "停用");
        return db;
    }

    /**
     * 检查部门存在成员
     *
     * @param id 部门ID
     */
    public void checkHasMember(Long id, String operate) {
        Preconditions.check(() -> userDeptService.exists(Wrappers.<UserDeptEntity>lambdaQuery()
                .eq(UserDeptEntity::getDeptId, id)), ErrorCode.DEPT_HAS_MEMBER, operate);
    }

    /**
     * 判断是否可启用
     *
     * @param id 部门ID
     * @return 部门信息
     */
    public DeptEntity canEnable(Long id) {
        DeptEntity db = checkExists(id);
        Preconditions.check(() -> EnableStatus.ENABLE.match(db.getStatus()), ErrorCode.DEPT_DISABLED);
        checkParentActive(db.getPid());
        return db;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void disable(Long id) {
        update(Wrappers.<DeptEntity>lambdaUpdate()
                .set(DeptEntity::getStatus, EnableStatus.DISABLE.getCode())
                .eq(DeptEntity::getId, id)
                .eq(DeptEntity::getStatus, EnableStatus.ENABLE.getCode()));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void enable(Long id) {
        update(Wrappers.<DeptEntity>lambdaUpdate()
                .set(DeptEntity::getStatus, EnableStatus.ENABLE.getCode())
                .eq(DeptEntity::getId, id)
                .eq(DeptEntity::getStatus, EnableStatus.DISABLE.getCode()));
    }

}
